package com.pap.gateway.filter.sentinel;

import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayRegexCache;
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.function.Predicate;
import com.pap.base.constant.JWTConstants;
import com.pap.base.dto.jwt.JWTUserDTO;
import com.pap.base.util.jwt.JWTTokenUtilss;
import com.pap.base.util.string.StringUtilss;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 自定义 网关限流参数解析器，代码参考 com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser
 *
 * 再对应的 parse*  方法中增加代码进行参数解析： 示例可以针对 header 中的 papToken 进行 JWT 解析，解析出来 租户编号 进行返回
 * 这样就可以针对 租户 进行限流。
 *
 * @param <T>
 */
public class PapGatewayParamParser<T> {
    private final RequestItemParser<T> requestItemParser;

    public PapGatewayParamParser(RequestItemParser<T> requestItemParser) {
        AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null");
        this.requestItemParser = requestItemParser;
    }

    public Object[] parseParameterFor(String resource, T request, Predicate<GatewayFlowRule> rulePredicate) {
        if (!StringUtil.isEmpty(resource) && request != null && rulePredicate != null) {
            Set<GatewayFlowRule> gatewayRules = new HashSet();
            Set<Boolean> predSet = new HashSet();
            boolean hasNonParamRule = false;
            Iterator var7 = GatewayRuleManager.getRulesForResource(resource).iterator();

            while(var7.hasNext()) {
                GatewayFlowRule rule = (GatewayFlowRule)var7.next();
                if (rule.getParamItem() != null) {
                    gatewayRules.add(rule);
                    predSet.add(rulePredicate.test(rule));
                } else {
                    hasNonParamRule = true;
                }
            }

            if (!hasNonParamRule && gatewayRules.isEmpty()) {
                return new Object[0];
            } else if (predSet.size() <= 1 && !predSet.contains(false)) {
                int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size();
                Object[] arr = new Object[size];

                int idx;
                String param;
                for(Iterator var9 = gatewayRules.iterator(); var9.hasNext(); arr[idx] = param) {
                    GatewayFlowRule rule = (GatewayFlowRule)var9.next();
                    GatewayParamFlowItem paramItem = rule.getParamItem();
                    idx = paramItem.getIndex();
                    param = this.parseInternal(paramItem, request);
                }

                if (hasNonParamRule) {
                    arr[size - 1] = "$D";
                }

                return arr;
            } else {
                return new Object[0];
            }
        } else {
            return new Object[0];
        }
    }

    private String parseInternal(GatewayParamFlowItem item, T request) {
        switch(item.getParseStrategy()) {
            case 0:
                return this.parseClientIp(item, request);
            case 1:
                return this.parseHost(item, request);
            case 2:
                return this.parseHeader(item, request);
            case 3:
                return this.parseUrlParameter(item, request);
            case 4:
                return this.parseCookie(item, request);
            default:
                return null;
        }
    }

    private String parseClientIp(GatewayParamFlowItem item, T request) {
        String clientIp = this.requestItemParser.getRemoteAddress(request);
        String pattern = item.getPattern();
        return StringUtil.isEmpty(pattern) ? clientIp : this.parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern);
    }

    private String parseHeader(GatewayParamFlowItem item, T request) {
        String headerKey = item.getFieldName();
        String pattern = item.getPattern();
        String headerValue = this.requestItemParser.getHeader(request, headerKey);
        // TODO code : add jwt header parse
        if(StringUtilss.isNotEmpty(headerKey) && StringUtilss.isNotEmpty(headerValue) &&
                headerKey.equals("papToken") && headerValue.startsWith("Bearer ")) {
            String jwtStr = headerValue.substring(7);
            JWTUserDTO jwtUserDTO = JWTTokenUtilss.parse(jwtStr, JWTConstants.SECRET);
            headerValue = jwtUserDTO.getTenantId();
        }
        return StringUtil.isEmpty(pattern) ? headerValue : this.parseWithMatchStrategyInternal(item.getMatchStrategy(), headerValue, pattern);
    }

    private String parseHost(GatewayParamFlowItem item, T request) {
        String pattern = item.getPattern();
        String host = this.requestItemParser.getHeader(request, "Host");
        return StringUtil.isEmpty(pattern) ? host : this.parseWithMatchStrategyInternal(item.getMatchStrategy(), host, pattern);
    }

    private String parseUrlParameter(GatewayParamFlowItem item, T request) {
        String paramName = item.getFieldName();
        String pattern = item.getPattern();
        String param = this.requestItemParser.getUrlParam(request, paramName);
        return StringUtil.isEmpty(pattern) ? param : this.parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
    }

    private String parseCookie(GatewayParamFlowItem item, T request) {
        String cookieName = item.getFieldName();
        String pattern = item.getPattern();
        String param = this.requestItemParser.getCookieValue(request, cookieName);
        return StringUtil.isEmpty(pattern) ? param : this.parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
    }

    private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) {
        if (value == null) {
            return null;
        } else {
            switch(matchStrategy) {
                case 0:
                    return value.equals(pattern) ? value : "$NM";
                case 1:
                default:
                    return value;
                case 2:
                    Pattern regex = GatewayRegexCache.getRegexPattern(pattern);
                    if (regex == null) {
                        return value;
                    }

                    return regex.matcher(value).matches() ? value : "$NM";
                case 3:
                    return value.contains(pattern) ? value : "$NM";
            }
        }
    }
}
